﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using BitMiracle.LibTiff.Classic;
/*
 * 20130122 add RoundTo property
 * 
 * 
 * 
 * */
namespace wicDIG
{
    public static class UtilDig
    {
        public static mainWindow Win;
        public static Matrix trans;
        public static Matrix iTrans;

        public static Point nowOnPic;
        public static Point prevOnPic;
        public static int zoomFactor;
        /// <summary>
        /// Flag to determine when to set the transformation matrix
        /// </summary>
        public static bool[] CoordSet;
        public static PointF[] LastBCoords;
        public static Point[] LastPCoords;
        public static List<PointF> RecordedPoints;

        public static Font myFont;
        public static Brush myBrush;
        public static Pen myPen;

        public static bool ShowGrid;
        public static int miniSize;
        /// <summary>
        /// these two parameters account for auto generated grid point calculation
        /// </summary>
        public static float GridInterval;
        public static float GridOffset;
        public static float RoundTo;
        
        public static CoordStatus coordMode;
        public static string textFormat;
        public static int precision;

        public static string stitchFileName;
        public static SolidBrush stitchBackground;
        /// <summary>
        /// Initialize UtilDig, at the start of the program
        /// </summary>
        public static void init(mainWindow m)
        {
            Win = m;
            trans = new Matrix(3);
            trans.SetUnit();
            iTrans = trans;

            nowOnPic = new Point();
            CoordSet = new bool[2];
            LastBCoords = new PointF[3];
            LastPCoords = new Point[3];
            RecordedPoints = new List<PointF>();
            myFont = new Font("Arial", 18);
            myBrush = new SolidBrush(Color.FromArgb(100,Color.GreenYellow));
            myPen = new Pen(myBrush);
            prevOnPic = new Point(-1, -1); ;
            GridInterval = 500;
            GridOffset = -250;
            coordMode = CoordStatus.ThreePoints;
            textFormat = "%number%";
            zoomFactor = 100; //1:1
            precision = 3;
            RoundTo = 50;
            ShowGrid = true;
            miniSize = 50;

            stitchBackground = new SolidBrush(Color.White);
            stitchFileName = "stitch.tif";

            Win.initCustom();
        }
        public static void ChangeColor(Color c)
        {
            myBrush = new SolidBrush(c);
            myPen = new Pen(myBrush);
            Win.ChangeButtonColor(c);
        }
        public static void ChangeWidth(float w)
        {
            myPen.Width = w;
            Win.ChangeWidthSelection(w);

        }
        
        public static string Point2String(Point p)
        {
            return System.String.Format("({0},{1})",new object[]{p.X.ToString(),p.Y.ToString()});
        }
        public static string Point2String(PointF p)
        {
            return System.String.Format("({0},{1})", new object[] { p.X.ToString(), p.Y.ToString() });
        }
        /// <summary>
        /// Image point coordinate to board coordinate.
        /// </summary>
        /// <param name="pOnPic"></param>
        /// <returns></returns>
        public static PointF P2B(Point pOnPic)
        {
            Matrix affineCoord = Vec2AffCoord(pOnPic);
            
            affineCoord = trans * affineCoord;
            return new PointF((float)affineCoord[0, 0], (float)affineCoord[1, 0]);
        }
        public static Point B2P(PointF pOnBoard)
        {
            Matrix aff = Vec2AffCoord(pOnBoard);
            aff = iTrans * aff;
            return new Point((int)aff[0, 0], (int)aff[1, 0]);
        }
        /// <summary>
        /// convert a 2D point coordinate (x,y) to affine transformation coordinate [x,y,1]'
        /// </summary>
        /// <param name="pOnPic"></param>
        /// <returns></returns>
        public static Matrix Vec2AffCoord(Point pOnPic)
        {
            Matrix affineCoord = new Matrix(3, 1);
            affineCoord[0, 0] = pOnPic.X;
            affineCoord[1, 0] = pOnPic.Y;
            affineCoord[2, 0] = 1;
            return affineCoord;
        }
        public static Matrix Vec2AffCoord(PointF pOnPic)
        {
            Matrix affineCoord = new Matrix(3, 1);
            affineCoord[0, 0] = pOnPic.X;
            affineCoord[1, 0] = pOnPic.Y;
            affineCoord[2, 0] = 1;
            return affineCoord;
        }
        /// <summary>
        /// Set the origin of transformation frame
        /// </summary>
        public static void Set1stCoordinate()
        {
            Point onPic = nowOnPic;
            CoordDialog cd = new CoordDialog();
            cd.Set("Set 1st Point", LastBCoords[0]);
            DialogResult dr = cd.ShowDialog();
            if (dr == DialogResult.Cancel)
            {
                Win.WarningUpdate("Setting Point operation cancelled.");
                return;
            }
            else
            {
                LastBCoords[0] = cd.coord;
                LastPCoords[0] = onPic;
                trans[0, 2] = 0; trans[1, 2] = 0;
                Matrix bor = trans * Vec2AffCoord(onPic);
                trans[0, 2] = LastBCoords[0].X - bor[0, 0];
                trans[1, 2] = LastBCoords[0].Y - bor[1, 0];
                CoordSet[0] = false;
                CoordSet[1] = false;
                iTrans = trans.Inverse();//enable grid
                Win.MessageUpdate("First point set to " + Point2String(LastBCoords[0]) + ".") ;
            }
            return;
        }
        /// <summary>
        /// extrapolate the possible coordinate by the onPic point
        /// because the transformation scale is often fixed, and the set points are usually a multiple of 50
        /// </summary>
        /// <param name="onPic"></param>
        /// <returns></returns>
        public static PointF GuessCoord(Point onPic)
        {
            PointF guessed = P2B(onPic);
            guessed.X = (float)Math.Round(guessed.X / RoundTo) * RoundTo;
            guessed.Y = (float)Math.Round(guessed.Y / RoundTo) * RoundTo;
            return guessed;
        }
        /// <summary>
        /// Check for CoordSet to see if all 3 coordinates are set
        /// then transformation matrix can be determined
        /// </summary>
        public static void TrySetTrans()
        {
            if (CoordSet[0] && CoordSet[1])
            {
                Matrix trans2DP = new Matrix(2);
                trans2DP[0, 0] = LastPCoords[1].X - LastPCoords[0].X; trans2DP[0, 1] = LastPCoords[2].X - LastPCoords[0].X;
                trans2DP[1, 0] = LastPCoords[1].Y - LastPCoords[0].Y; trans2DP[1, 1] = LastPCoords[2].Y - LastPCoords[0].Y;
                Matrix trans2DB = new Matrix(2);
                trans2DB[0, 0] = LastBCoords[1].X - LastBCoords[0].X; trans2DB[0, 1] = LastBCoords[2].X - LastBCoords[0].X;
                trans2DB[1, 0] = LastBCoords[1].Y - LastBCoords[0].Y; trans2DB[1, 1] = LastBCoords[2].Y - LastBCoords[0].Y;
                 Matrix trans2D;
                try
                {
                    double d = trans2DP.Det();
                    Matrix iP = new Matrix(2);
                    iP[0, 0] = trans2DP[1, 1]; iP[0, 1] = -trans2DP[0, 1];
                    iP[1, 0] = -trans2DP[1, 0]; iP[1, 1] = trans2DP[0, 0];
                    iP = (1/d) * iP;
                    trans2D = trans2DB * iP;
                }
                catch (Exception)
                {
                    Win.WarningUpdate("The 3 points are insufficient. Please set coordinates again.");
                    return;
                }
                trans.SetUnit();
                trans[0, 0] = trans2D[0, 0]; trans[0, 1] = trans2D[0, 1]; trans[0, 2] = 0;
                trans[1, 0] = trans2D[1, 0]; trans[1, 1] = trans2D[1, 1]; trans[1, 2] = 0;
                //adjusting origin [0]
                Matrix bor = trans * Vec2AffCoord(LastPCoords[0]);
                trans[0, 2] = LastBCoords[0].X - bor[0, 0];
                trans[1, 2] = LastBCoords[0].Y - bor[1, 0];
                iTrans = trans.Inverse();
                Win.MessageUpdate("New transformation set.");
                
            }
            else
            {
                Win.MessageUpdate("Set the rest coordinate to define a new transformation.");
            }
        }
        public static void Set2ndCoordinate()
        {
            Point onPic = nowOnPic;
            CoordDialog cd = new CoordDialog();
            cd.Set("Set 2nd Point", GuessCoord(onPic));
            DialogResult dr = cd.ShowDialog();
            if (dr == DialogResult.Cancel)
            {
                Win.WarningUpdate("Setting Point operation cancelled.");
                return;
            }
            else
            {
                LastBCoords[1] = cd.coord;
                LastPCoords[1] = onPic;
                CoordSet[0] = true;
                Win.MessageUpdate("Second point set to " + Point2String(LastBCoords[1]) + ".");
                if (coordMode == CoordStatus.Orthogonal)
                {
                    LastBCoords[2].X = LastBCoords[0].X - (LastBCoords[1].Y - LastBCoords[0].Y);
                    LastBCoords[2].Y = LastBCoords[0].Y + (LastBCoords[1].X - LastBCoords[0].X);

                    LastPCoords[2].X = LastPCoords[0].X + (LastPCoords[1].Y - LastPCoords[0].Y);
                    LastPCoords[2].Y = LastPCoords[0].Y - (LastPCoords[1].X - LastPCoords[0].X);
                    CoordSet[1] = true;
                }
                TrySetTrans();
            }
        }
        public static void Set3rdCoordinate()
        {
            Point onPic = nowOnPic;
            CoordDialog cd = new CoordDialog();
            cd.Set("Set 3rd Point", GuessCoord(onPic));
            DialogResult dr = cd.ShowDialog();
            if (dr == DialogResult.Cancel)
            {
                Win.WarningUpdate("Setting Point operation cancelled.");
                return;
            }
            else
            {
                LastBCoords[2] = cd.coord;
                LastPCoords[2] = onPic;
                CoordSet[1] = true;
                Win.MessageUpdate("Third point set to " + Point2String(LastBCoords[2]) + ".");
                if (coordMode == CoordStatus.Orthogonal)
                {
                    LastBCoords[1].X = LastBCoords[0].X - (LastBCoords[1].Y - LastBCoords[0].Y);
                    LastBCoords[1].Y = LastBCoords[0].Y + (LastBCoords[1].X - LastBCoords[0].X);

                    LastPCoords[1].X = LastPCoords[0].X + (LastPCoords[1].Y - LastPCoords[0].Y);
                    LastPCoords[1].Y = LastPCoords[0].Y - (LastPCoords[1].X - LastPCoords[0].X);
                    CoordSet[0] = true;
                }
                TrySetTrans();
            }
        }
        public static void ClearCoordinates()
        {
            trans.SetUnit();
            Win.MessageUpdate("Coordinates cleared.");
        }
        public static Point PointOffset(Point p, int dx, int dy)
        {
            p.Offset(dx, dy);
            return p;
        }
        public static double PointDistance(PointF p, PointF q)
        {
            return Math.Sqrt((p.X - q.X) * (p.X - q.X) + (p.Y - q.Y) * (p.Y - q.Y));
        }
        
        public static void SaveElphyCompatible(Image img,string fullFileName)
        {
            
            using (Bitmap bmp = new Bitmap(img))
            {
               
                using (Tiff tif = Tiff.Open(fullFileName, "w"))
                {
                    byte[] raster = getImageRasterBytes(bmp, PixelFormat.Format24bppRgb);
                    tif.SetField(TiffTag.IMAGEWIDTH, bmp.Width);
                    tif.SetField(TiffTag.IMAGELENGTH, bmp.Height);
                    tif.SetField(TiffTag.COMPRESSION, Compression.LZW);
                    tif.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);

                    tif.SetField(TiffTag.ROWSPERSTRIP, bmp.Height);

                    tif.SetField(TiffTag.XRESOLUTION, bmp.HorizontalResolution);
                    tif.SetField(TiffTag.YRESOLUTION, bmp.VerticalResolution);

                    tif.SetField(TiffTag.BITSPERSAMPLE, 8);
                    tif.SetField(TiffTag.SAMPLESPERPIXEL, 3);

                    tif.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);

                    int stride = raster.Length / bmp.Height;
                    convertSamples(raster, bmp.Width, bmp.Height);

                    for (int i = 0, offset = 0; i < bmp.Height; i++)
                    {
                        tif.WriteScanline(raster, offset, i, 0);
                        offset += stride;
                    }
                }

                System.Diagnostics.Process.Start(fullFileName);
            }
            
        }
        private static byte[] getImageRasterBytes(Bitmap bmp, PixelFormat format)
        {
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            byte[] bits = null;

            try
            {
                // Lock the managed memory
                BitmapData bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, format);

                // Declare an array to hold the bytes of the bitmap.
                bits = new byte[bmpdata.Stride * bmpdata.Height];

                // Copy the values into the array.
                System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, bits, 0, bits.Length);

                // Release managed memory
                bmp.UnlockBits(bmpdata);
            }
            catch
            {
                return null;
            }

            return bits;
        }

        /// <summary>
        /// Converts BGR samples into RGB samples
        /// </summary>
        private static void convertSamples(byte[] data, int width, int height)
        {
            int stride = data.Length / height;
            const int samplesPerPixel = 3;

            for (int y = 0; y < height; y++)
            {
                int offset = stride * y;
                int strideEnd = offset + width * samplesPerPixel;

                for (int i = offset; i < strideEnd; i += samplesPerPixel)
                {
                    byte temp = data[i + 2];
                    data[i + 2] = data[i];
                    data[i] = temp;
                }
            }
        }
        private static ImageCodecInfo GetEncoderInfo(string mimeType)
        {
            int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; ++j)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }

        public static Image RotateImg(Image b, float angle)
        {

            angle = (float)Math.IEEERemainder(angle, 360.0);
            



            //弧度转换

            double radian = angle * Math.PI / 180.0;

            double cos = Math.Cos(radian);

            double sin = Math.Sin(radian);



            //原图的宽和高

            int w = b.Width;

            int h = b.Height;

            int W = (int)(Math.Max(Math.Abs(w * cos - h * sin), Math.Abs(w * cos + h * sin)));

            int H = (int)(Math.Max(Math.Abs(w * sin - h * cos), Math.Abs(w * sin + h * cos)));



            //目标位图

            Bitmap dsImage = new Bitmap(W, H);

            System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(dsImage);

            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Bilinear;

            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;



            //计算偏移量

            Point Offset = new Point((W - w) / 2, (H - h) / 2);



            //构造图像显示区域：让图像的中心与窗口的中心点一致

            Rectangle rect = new Rectangle(Offset.X, Offset.Y, w, h);

            Point center = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);



            g.TranslateTransform(center.X, center.Y);

            g.RotateTransform(360 - angle);



            //恢复图像在水平和垂直方向的平移

            g.TranslateTransform(-center.X, -center.Y);

            g.DrawImage(b, rect);



            //重置绘图的所有变换

            g.ResetTransform();



            g.Save();

            g.Dispose();

            //保存旋转后的图片

            b.Dispose();
            return dsImage;
        }
        public static float GetAxesRotation()
        {
            return (float)(Math.Atan2(trans[1, 0], trans[0, 0]) * 180 / Math.PI);
        }


    }
}
